/**
 * @description: 严格类型判断
 * @param {any} typeObject
 * @return {String} Object Array
 */
function toClass(typeObject) {
    return Object.prototype.toString
      .call(typeObject)
      .split(" ")[1]
      .replace("]", "");
  }
  
  /**
   * @description: 严格类型判断
   * @param {any} typeObject
   * @param {String} type
   * @return {Boolean}
   */
  function isType(typeObject, type) {
    let targetType = toClass(typeObject);
    return targetType === type;
  }
  
  /**
   * @description: 查找key是否在对象上（不包括对象原型）
   * @param {Object} obj
   * @param {String} key
   * @param {Boolean} typeVf 是否对obj参数进行强类型验证
   * @return {Boolean}
   */
  function hasOwn(obj, key, typeVf = true) {
    if (obj) {
      if ((typeVf && isType(obj, "Object")) || !typeVf) {
        return Object.hasOwnProperty.call(obj, key);
      }
    } else {
      throw Error("the obj params is required");
    }
  }
  
  /**
   * @description: 字符串去除空格
   * @param {String} str
   * @param {String} insetAuto 是否去除字符串内部空白符
   * @return {String}
   */
  function trimAll(str, insetAuto) {
    if (str) {
      if (!insetAuto) {
        return str.trim();
      } else {
        return str.replace(/\s/gm, "");
      }
    } else {
      return str;
    }
  }
  
/**
 * This is just a simple version of deep copy
 * Has a lot of edge cases bug
 * If you want to use a perfect deep copy, use lodash's _.cloneDeep
 * @param {Object} source
 * @returns {Object}
 */
 function deepClone(source) {
  if (!source && typeof source !== 'object') {
    throw new Error('error arguments', 'deepClone')
  }
  const targetObj = source.constructor === Array ? [] : {}
  Object.keys(source).forEach(keys => {
    if (source[keys] && typeof source[keys] === 'object') {
      targetObj[keys] = deepClone(source[keys])
    } else {
      targetObj[keys] = source[keys]
    }
  })
  return targetObj
}


  /**
   * @description: 深拷贝
   * @param {Object} jsonObject
   * @return {Object}
   */
  
  function extend(jsonObject) {
    return JSON.parse(JSON.stringify(jsonObject));
  }
  
  /**
   * @description: 混入
   * @param {Object} to
   * @param {Object} from
   * @return {Object}
   */
  function merge(to, from) {
    if (!from) return to;
    let key, toVal, fromVal;
    const keys = Object.keys(from);
    for (let i = 0, len = keys.length; i < len; i++) {
      key = keys[i];
      // in case the object is already observed...
      if (key === "__ob__") continue;
      toVal = to[key]; // child
      fromVal = from[key]; // parent
      if (!hasOwn(to, key)) {
        to[key] = fromVal; //若to没有此key，添加它
      } else if (
        toVal !== fromVal &&
        isType(toVal, "Object") &&
        isType(fromVal, "Object")
      ) {
        //若to有此key，且不等
        //若to有此key，值非对象，否则进行深度合并
        merge(toVal, fromVal);
      }
    }
    return to;
  }
  
  /**
   * @description: 数组合并(去重) 依赖Set对象
   * @param {Array} originArr
   * @param {Array} mergeArr
   * @param {String} deepKey 对 Array item为Object类型时，进行深度去重。（ 默认情况下如果原数组与合并数组的对象item指向同一个内存，不会产生合并 ）
   * @return {Object} [ union//合集 , intersection//交集 ,diff// 差集 ]
   */
  
  function union(originArr, mergeArr, deepKey) {
    if (originArr && isType(originArr, "Array")) {
      const union = [];
      const intersection = [];
      const diff = [];
      if (mergeArr && isType(originArr, "Array")) {
        if (mergeArr.length) {
          const originArr_set = new Set(originArr);
          const mergeArr_set = new Set(mergeArr);
          //有指定去重key的情况  且  合并item为对象类型时
          let deepKeyHas = false;
          let originArr_ObjectKeys;
          if (deepKey && typeof deepKey === "string") {
            deepKeyHas = true;
            originArr.forEach((item) => {
              if (item && isType(item, Object)) {
                originArr_ObjectKeys.push(item[deepKey]);
              }
            });
          }
          mergeArr_set.forEach((mergeItem) => {
            if (originArr_set.has(mergeItem)) {
              intersection.push(mergeItem);
            } else {
              if (deepKeyHas && isType(mergeItem, "Object")) {
                if (originArr_ObjectKeys.include(mergeItem[deepKey])) {
                  intersection.push(mergeItem);
                } else {
                  diff.push(mergeItem);
                }
              } else {
                diff.push(mergeItem);
              }
            }
          });
        }
        union.push(...originArr, ...diff);
        return {
          union,
          intersection,
          diff,
        };
      } else {
        throw Error("params mergeArr is required or type be Array");
      }
    } else {
      throw Error("params originArr is required or type be Array");
    }
  }
  
  /**
   * @description: await-to  同步处理await运算符错误
   * @param {Promise} promise
   * @param {Object} errorExt
   * @return {Array} [ error , result ]
   * @git https://github.com/scopsy/await-to-js
   */
  function to(promise, errorExt) {
    return promise
      .then((data) => [null, data])
      .catch((err) => {
        if (errorExt) {
          Object.assign(err, errorExt);
        }
        return [err, undefined];
      });
  }
  
  /**
   * @description: 防抖工厂函数
   * @param {Function} fn
   * @param {Number} delay
   * @return {Function}
   */
  function debounce(fn, delay) {
    let timeout = null; // 创建一个标记用来存放定时器的返回值
    return function () {
      clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
      timeout = setTimeout(() => {
        // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话，就不会执行 fn 函数
        fn.apply(this, arguments);
      }, delay);
    };
  }
  
  /**
   * @description: 节流工厂函数
   * @param {Function} fn
   * @param {Number} delay
   * @return {Function}
   */
  function throttle(fn, delay) {
    let canRun = true; // 通过闭包保存一个标记
    return function () {
      if (!canRun) return; // 在函数开头判断标记是否为true，不为true则return
      canRun = false; // 立即设置为false
      setTimeout(() => {
        // 将外部传入的函数的执行放在setTimeout中
        fn.apply(this, arguments);
        // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false，在开头被return掉
        canRun = true;
      }, delay);
    };
  }
  

  module.exports = {
    deepClone,
    extend,
    merge,
    union,
    toClass,
    isType,
    hasOwn,
    trimAll,
    to,
    debounce,
    throttle,
  }